#include "Game.h"
#include <vector>

Color palGlass  = Color(1.0f, 1.0f, 1.0f, 0.5f);
Color palWhite  = Color::FromRGB(196.0f, 196.0f,200.0f);
Color palGrey   = Color::FromRGB(136.0f, 136.0f,140.0f);
Color palBrown  = Color::FromRGB(255.0f, 128.0f,  0.0f);
Color palBlack  = Color::FromRGB( 16.0f,  16.0f, 20.0f);
Color palGreenD = Color::FromRGB( 22.0f,  48.0f, 35.0f);
Color palBlue   = Color::FromRGB(  0.0f,  0.0f, 255.0f);
Color palGreenM = Color::FromRGB( 43.0f,  79.0f, 57.0f);
Color palGreenL = Color::FromRGB( 88.0f, 163.0f, 88.0f);
Color palRed    = Color::FromRGB(153.0f,  33.0f,  0.0f);
const float PI = 3.1415926f;
const double BEAT = 0.2142857142857143;

const float S = TILE_SIZE;
const float HS = TILE_SIZE / 2.0f;

const float LIGHTEN_MAX = 2.0f;
const float LIGHTEN_DEC = 0.1f;

float Tower::mLighten = 0.0f;

/// Effects

float rndF() 
{
  return rand()%1000 / 1000.0f;
}

struct Shot : Obj
{
  Obj* From;
  Obj* To;

	virtual int Type() { return 'Shot'; }

  Shot(Obj* from, Obj* to) : From(from), To(to) {}

  void DrawLightning(Vec a, Vec b, float disp, Color cl)
  {
    if (disp < 5) {
      DrawLine(a.X, a.Y, b.X, b.Y, cl, rndF() * 2.0f + 1.0f);
    }
    else {
      Vec mid((a.X + b.X) / 2.0f, (a.Y + b.Y) / 2.0f);
      mid.X += (rndF() - 0.5f) * disp;
      mid.Y += (rndF() - 0.5f) * disp;
      DrawLightning(a, mid, disp / 2.0f, cl);
      DrawLightning(b, mid, disp / 2.0f, cl);
    }
  }
};

struct Bolt : Shot
{
  float Dur;
  float Alpha;
  Vec FromPos;
  Vec ToPos;

	virtual int Type() { return 'Bolt'; }

  Bolt(Obj* from, Obj* to, float dur) : Shot(from, to), Dur(dur), Alpha(0) {
    FromPos = from->Pos;
    ToPos = to->Pos;
    if(from->Dead || to->Dead)
      Dead = true;
  }

	void Update(float dt)
  { 
    Life += dt; 
    Alpha = sin((Life / Dur) * PI);

    if(From && !From->Dead)
      FromPos = From->Pos;
    else
      From = 0;

    if(To && !To->Dead)
      ToPos = To->Pos;
    else
      To = 0;

    if(Life > Dur) 
      Dead = true;
  }

  void Render(const Vec& offset)
  {
		srand((int)this + int((TotalGameTime+Life)*20));
    DrawLightning(FromPos, ToPos, 40, palBlue.Lighten(rndF()));
  }
};

struct Spark : Shot
{
  bool Big;

	virtual int Type() { return 'Sprk'; }

  Spark(Obj* from, Obj* to, bool big = false) : Shot(from, to), Big(big) {
    Pos = from->Pos;
  }

	void Update(float dt)
  { 
    Life += dt;
    float speed = (Big ? 0.1f : 0.2f) + Life;
    Pos.X += (To->Pos.X - Pos.X) * speed;
    Pos.Y += (To->Pos.Y - Pos.Y) * speed;

    if(To->Dead || Pos.Distance(To->Pos) < 2.0f) 
      Dead = true;
  }

  void Render(const Vec& offset)
  { 
    const float radius = 20.0f;
    for(int i=0; i<3; i++) {
      float angle = rndF() * PI * 2.0f;
      Vec rndP(Pos.X + cos(angle) * radius, Pos.Y + sin(angle) * radius);
      DrawLightning(Pos, rndP, 40, palBlue.Lighten(rndF()));
    }
    DrawFramedCircle(Pos.X, Pos.Y, Big ? 7.0f : 4.0f, palBlue.Lighten(rndF()), Color::Blue().Lighten(0.5f), 1.0f);
  }
};

/// Game

void Game::Render()
{
	World::Render();

	double bounce = (TaktPercent*2*3.1415);

  if (WaveState == WAVE_BREATH_BETWEEN_WAVES)
		DrawFntCenterBounce(FontLarge, SCREEN_WIDTH/2, 20, Color::Red(), 10, 0.4f, bounce, "Next Wave in %u!", (unsigned)NextWaveTime);
	else if (WaveState == WAVE_WAITING_FOR_DEAD)
		DrawFntCenterBounce(FontLarge, SCREEN_WIDTH/2, 20, Color::Red(), 10, 0.4f, bounce, "Kill Them All!");
	else if (WaveState == WAVE_SPAWNING)
		DrawFntCenterBounce(FontLarge, SCREEN_WIDTH/2, 20, Color::Red(), 10, 0.4f, bounce, "Here They Come!");

	DrawFntCenterBounce(FontLarge, 250, SCREEN_HEIGHT - 60, Color::Yellow(0.5f), 10, 0.4f, bounce+0.1, "Money: $%u", Money.Display());
}

void Game::InitGraphics()
{
  FontSmall = LoadFnt("Data/Square.ttf", 10);
  FontMedium = LoadFnt("Data/Square.ttf", 24);
  FontLarge = LoadFnt("Data/Square.ttf", 48);
}

void Game::AnimEvent(const char* event, const Prop& arg)
{
}

/// Tile

struct Neighbour {
  Dirs Dir;
  size_t X, Y;
  Vec Border;
  Vec Inside;
  bool Exists;
};

Vec ToUpperLeft(Vec vec)
{
  vec.X -= HS;
  vec.Y -= HS;
  return vec;
}

void Tile::DrawRoad(const Vec& offset)
{
  float margin = 2.0f;
  float thick = 5.0f;

  Neighbour neighbours[] = {
    {UP,    X, Y - 1, Vec(TILE_SIZE / 2.0f, 0.0f), Vec(TILE_SIZE / 2.0f, margin)},
    {RIGHT, X + 1, Y, Vec(TILE_SIZE, TILE_SIZE / 2.0f), Vec(TILE_SIZE - margin, TILE_SIZE / 2.0f)},
    {DOWN,  X, Y + 1, Vec(TILE_SIZE / 2.0f, TILE_SIZE), Vec(TILE_SIZE / 2.0f, TILE_SIZE - margin)},
    {LEFT,  X - 1, Y, Vec(0.0f, TILE_SIZE / 2.0f), Vec(margin, TILE_SIZE / 2.0f)}
  };

  Vec vFirst(-1.0f, -1.0f);
  Vec vSecond(-1.0f, -1.0f);
  for(int i=0; i<4; i++) {
    Neighbour& n = neighbours[i];
    if(!GetLevel().Tiles.IsValid(n.X, n.Y)) {
      n.Exists = false;
      continue;
    }
    int nType = GetLevel().Tiles(n.X, n.Y)->TileType;
    n.Exists = nType == TILE_ROAD || nType == TILE_BASE || nType == TILE_SPAWN;
    if(n.Exists) {
      DrawLine(Pos.X + n.Border.X + offset.X, Pos.Y + n.Border.Y + offset.Y, 
               Pos.X + n.Inside.X + offset.X, Pos.Y + n.Inside.Y + offset.Y, 
               palGreenL, thick);
      if(vFirst.X < 0.0f)
        vFirst = n.Inside;
      else
        vSecond = n.Inside;
    }
  }

  if(vSecond.X < 0.0f)
    vSecond = Vec(TILE_SIZE / 2.0f, TILE_SIZE / 2.0f);

  DrawLine(Pos.X + vFirst.X + offset.X, Pos.Y + vFirst.Y + offset.Y, 
           Pos.X + vSecond.X + offset.X, Pos.Y + vSecond.Y + offset.Y, 
           palGreenL, thick);
}

void Tile::DrawSpawn(const Vec& offset)
{
  Vec center = Vec(Pos.X + TILE_SIZE / 2.0f + offset.X, Pos.Y + TILE_SIZE / 2.0f + offset.Y);
  DrawFramedCircle(center.X, center.Y, TILE_SIZE / 2.0f - 3, palRed, palRed.Lighten(-0.5f), 3.0f);
  DrawFramedCircle(center.X, center.Y, TILE_SIZE / 2.0f - 10, palRed.Lighten(0.5f), palRed.Lighten(1.0f), 3.0f);
}

void Tile::DrawBase(const Vec& offset)
{
  Vec margin(0.0f, 10.0f);
  int pins = 10;
  float thick = 1.0f;

  for(int i=0; i<pins; i++) {
    float x = Pos.X + offset.X + margin.X + i * ((TILE_SIZE - margin.X * 2) / (pins - 1));
    DrawLine(x, Pos.Y + offset.Y + margin.Y / 2.0f, x, Pos.Y + offset.Y + TILE_SIZE - margin.Y / 2.0f,
             palGrey, thick);
  }

  Vec rectPos = Vec(Pos.X + margin.X + offset.X, Pos.Y + margin.Y + offset.Y);
  DrawFramedRect(rectPos.X, rectPos.Y, TILE_SIZE - 2 * margin.X, TILE_SIZE - 2 * margin.Y, 
                 palBlack, palBlack.Lighten(-0.5f), 3.0);  
  DrawFntCenter(GetGame().FontSmall, Pos.X + offset.X + TILE_SIZE / 2.0f, Pos.Y + offset.Y + TILE_SIZE / 2.5f,
                palWhite, "CHIP");
}

void Tile::Render(const Vec& offset)
{
  float x = Pos.X + offset.X;
  float y = Pos.Y + offset.Y;
  
	DrawRect(x, y, TILE_SIZE, TILE_SIZE, palGreenD.Lighten(Selected?0.5f:0.0));
	DrawFilledRect(x, y, TILE_SIZE, TILE_SIZE, palGreenM.Lighten(Selected?0.5f:0.0));

	switch (TileType)
	{
	case TILE_GRASS:
		break;
	case TILE_ROAD:
    DrawRoad(offset);
		break;
	case TILE_BASE:
    DrawRoad(offset);
		DrawBase(offset);
		break;
	case TILE_SPAWN:
    DrawRoad(offset);
		DrawSpawn(offset);
		break;
	}
}

/// Creeps

void Creep::DrawNormal(const Vec& offset)
{
  Vec margin(10.0f, 14.0f);

  // Splines
  float sinus = sin(BeatPercent * PI);

  Vec a(TILE_SIZE * 3.0f / 5.0f + sinus * 7.0f, 3.0f + sinus * 2.0f);
  Vec b(TILE_SIZE + 10, TILE_SIZE / 2.0f);
  Vec c(TILE_SIZE - b.X, b.Y);
  Vec d(TILE_SIZE - a.X, a.Y);
  Vec splineTop[4] = {a, b, c, d};
  Vec splineBot[4];
  for(int i=0; i<4; i++) {
    splineBot[i] = splineTop[i];
    splineBot[i].Y = TILE_SIZE - splineBot[i].Y;
  }
  DrawSpline(splineTop, palRed.Lighten(0.7f + Lighten), 2.0f);
  DrawSpline(splineBot, palRed.Lighten(0.7f + Lighten), 2.0f);

  // Body
  DrawFramedRect(margin.X, margin.Y, TILE_SIZE - margin.X * 2.0f, TILE_SIZE - margin.Y * 2.0f,
    palRed.Lighten(Lighten), palRed.Lighten(-0.5f + Lighten), 2.0f);
}

void Creep::DrawFlyer(const Vec& offset)
{
  // Splines
  float sinus = sin(BeatPercent * PI);

  Vec a(0.0f, TILE_SIZE / 2.0f);
  Vec b(0.0f, 0.0f);
  Vec c(TILE_SIZE - b.X, TILE_SIZE - b.Y);
  Vec d(TILE_SIZE - a.X, TILE_SIZE - a.Y);
  Vec splineH[4] = {a, b, c, d};
  Vec splineV[4];
  for(int i=0; i<4; i++) {
    splineV[i] = splineH[i];
    float tmp = splineV[i].X;
    splineV[i].X = -(splineV[i].Y - TILE_SIZE / 2.0f) + TILE_SIZE / 2.0f;
    splineV[i].Y = (tmp - TILE_SIZE / 2.0f) + TILE_SIZE / 2.0f;
  }
  DrawSpline(splineH, palRed.Lighten(0.7f + Lighten), 1.0f);
  DrawSpline(splineV, palRed.Lighten(0.7f + Lighten), 1.0f);

  // Body
  DrawFramedCircle(TILE_SIZE / 2.0f, TILE_SIZE / 2.0f, TILE_SIZE / 8.0f,
    palRed.Lighten(Lighten), palRed.Lighten(-0.5f + Lighten), 1.0f);
}

void Creep::DrawTank(const Vec& offset)
{
  float sinus = sin(BeatPercent * PI);
  if(sinus < 0.7f)
    sinus /= 2.0f;

  const Vec margin(2.0f, 5.0f + sinus * 2.0f);

  // Body
  DrawLine(margin.X, margin.Y * 2.5f, TILE_SIZE / 2.0f, margin.Y * 2.5f, palGrey.Lighten(Lighten), 4.0f);
  DrawLine(margin.X, TILE_SIZE - margin.Y * 2.5f, TILE_SIZE / 2.0f, TILE_SIZE - margin.Y * 2.5f, palGrey.Lighten(Lighten), 4.0f);
  DrawFramedRect(TILE_SIZE / 2.0f, margin.Y, TILE_SIZE / 2.0f, TILE_SIZE - margin.Y * 2,
    palRed.Lighten(Lighten), palRed.Lighten(-0.5f + Lighten), 2.0f);
  DrawFramedRect(TILE_SIZE / 2.0f - 10, 0.0f, 10, TILE_SIZE,
    palRed.Lighten(0.5f + Lighten), palRed.Lighten(Lighten), 2.0f);
  DrawLine(TILE_SIZE / 2.0f + 5.0f, margin.Y * 3.0f, TILE_SIZE / 2.0f + 5.0f, TILE_SIZE - margin.Y * 3.0f, 
           palRed.Lighten(-0.5f + Lighten), 2.0f);
}

void Creep::Render(const Vec& offset)
{
  static Pic pic = MakeRectangle(TILE_SIZE, TILE_SIZE, Color(1.0f, 0.0f, 1.0f, 1.0f));
  DrawToPic(pic);
  ClearScr(Color(1.0f, 0.0f, 1.0f, 0.0f));

  if(Lighten > 0.0f)
    Lighten -= LIGHTEN_DEC;
  else
    Lighten = 0.0f;

  switch(CreepType) 
  {
  case CREEP_NORMAL:
    DrawNormal(offset);
    break;
  case CREEP_FLYER:
    DrawFlyer(offset);
    break;
  case CREEP_TANK:
    DrawTank(offset);
    break;
  }

  DrawToScreen();
  if(CreepType == CREEP_FLYER)
    RealRot = Life * 10.0f;
  else
    RealRot += (Rot - RealRot) * 0.2f;

  Vec zero = Vec(Pos.X + offset.X, Pos.Y + offset.Y);
  DrawPic(pic, zero.X, zero.Y, RealRot, Color::White());

  zero.X -= TILE_SIZE / 2.0f;
  zero.Y -= TILE_SIZE / 2.0f;
  float pixelHp = Health / ( CreepData[CreepType].Life / (TILE_SIZE - 2.0));
  const float hpBarH = 5;
  DrawFramedRect(zero.X, zero.Y - 15, TILE_SIZE, hpBarH, palRed, palRed.Lighten(-0.5f));
	DrawFilledRect(zero.X + 1, zero.Y - 14, pixelHp, hpBarH - 2.0f, palGreenL.Lighten((1.0-TaktPercent)*0.5f));
}

void Creep::AnimEvent(const char* event, const Prop& arg) 
{
  if(strcmp("kill", event) == 0)
    Event('kill', Prop());

	else if (strcmp("base", event) == 0)
		Event('kill', Prop());

  else if(strcmp("spark", event) == 0)
    Wrld->AddToWorld(new Bolt(this, arg.Object, 0.2f));

  else if(strcmp("damage", event) == 0)
    Lighten = LIGHTEN_MAX;
}

/// TOWER

void Tower::DrawBeatRifle(const Vec& at) 
{
  const float holeR = 2.0f;
  DrawFilledCircle(at.X + 0.0f, at.Y + HS, holeR, palGrey.Lighten(mLighten));
  DrawFilledCircle(at.X + S, at.Y + HS, holeR, palGrey.Lighten(mLighten));
  DrawLine(at.X + 0.0f, at.Y + HS, at.X + S, at.Y + HS, palGrey.Lighten(mLighten), 2.0f);

  const Vec m(S / 4.0f, S / 3.0f);
  const float r = 3.0f;
  DrawFramedRoundRect(at.X + m.X, at.Y + m.Y, S - m.X * 2.0f, S - m.Y * 2.0f, r, r, palBrown, palBrown.Lighten(-0.5f + mLighten), 2.0f);
  DrawFilledRect(at.X + HS - 5, at.Y + m.Y, 3, S - 2*m.Y, palBlue.Lighten(mLighten));
  DrawFilledRect(at.X + HS + 2, at.Y + m.Y, 4, S - 2*m.Y, palRed.Lighten(mLighten));
}

void Tower::DrawChipstepCoil(const Vec& at)
{
  const Vec m(S / 3.0f, S / 5.0f);
  const float insideH = S / 3.0f;
  const float outsideH = (S - 2*m.Y - insideH) / 2.0f;
  DrawFramedRect(at.X + m.X, at.Y + m.Y, S - 2*m.X, outsideH, palGrey.Lighten(mLighten), palGrey.Lighten(-0.5f + mLighten), 2.0f);
  DrawFramedRect(at.X + m.X, at.Y + S - m.Y - outsideH, S - 2*m.X, outsideH, palGrey.Lighten(mLighten), palGrey.Lighten(-0.5f + mLighten), 2.0f);
  DrawFilledRect(at.X + m.X, at.Y + m.Y + outsideH, S - 2*m.X, insideH, palGlass.Lighten(mLighten));
}

void Tower::DrawSnareGun(const Vec& at)
{
  const Vec m(5.0f, 5.0f);
  const float pinR = 2.0f;
  DrawFilledCircle(at.X + m.X, at.Y + m.Y, pinR, palGrey.Lighten(mLighten));
  DrawFilledCircle(at.X + S - m.X, at.Y + m.Y, pinR, palGrey.Lighten(mLighten));
  DrawFilledCircle(at.X + HS, at.Y + S - m.Y, pinR, palGrey.Lighten(mLighten));
	DrawLine(at.X + m.X, at.Y + m.Y, at.X + HS, at.Y + HS, palGrey.Lighten(mLighten), 1.0f);
	DrawLine(at.X + S - m.X, at.Y + m.Y, at.X + HS, at.Y + HS, palGrey.Lighten(mLighten), 1.0f);
	DrawLine(at.X + HS, at.Y + S - m.Y, at.X + HS, at.Y + HS, palGrey.Lighten(mLighten), 1.0f);
  DrawFramedCircle(at.X + HS, at.Y + HS - 2.0f, HS / 2.0f, palGrey.Lighten(mLighten), palGrey.Lighten(-0.5f + mLighten), 2.0f);
  DrawFramedCircle(at.X + HS, at.Y + HS - 2.0f, HS / 2.5f, palGrey.Lighten(mLighten), palGrey.Lighten(-0.5f + mLighten), 2.0f);
}

void Tower::DrawBaseCannon(const Vec& at)
{
	const Vec m(1.0f, 1.0f);
  DrawLine(at.X, at.Y, at.X + S, at.Y + S, palBlack.Lighten(mLighten), 7.0f);
  DrawLine(at.X, at.Y + S, at.X + S, at.Y, palBlack.Lighten(mLighten), 7.0f);
  DrawFramedCircle(at.X + HS, at.Y + HS, HS - 3, palBlack.Lighten(mLighten), palGrey.Lighten(mLighten), 2.0f);
  DrawFramedCircle(at.X + HS, at.Y + HS, HS - 15.0f, palGrey.Lighten(mLighten), palGrey.Lighten(-0.5f + mLighten), 2.0f);
}

void Tower::Render(const Vec& offset)
{
	if (Selected)
		DrawRange(Pos + offset, Range);

  if(Lighten > 0.0f)
    Lighten -= LIGHTEN_DEC;
  else
    Lighten = 0.0f;
  mLighten = Lighten;

  DrawPreview(TowerType, Pos + offset);

  mLighten = 0;
}

void Tower::DrawRange(Vec pos, float range)
{
	DrawFilledCircle(pos.X, pos.Y, range + 3*cos(BeatPercent*2*3.14159), Color::White(0.1f));
}

void Tower::DrawPreview(int type, Vec at)
{
  at = ToUpperLeft(at);
  switch(type)
  {
  case TOWER_BEAT_RIFLE:
    DrawBeatRifle(at);
    break;
  case TOWER_CHIPSTEP_COIL:
    DrawChipstepCoil(at);
    break;
  case TOWER_SNARE_GUN:
    DrawSnareGun(at);
    break;
  case TOWER_BASE_CANNON:
    DrawBaseCannon(at);
    break;
  }
}

void Tower::AnimEvent(const char* event, const Prop& arg) 
{
  if(strcmp("fire", event) == 0)
  {
    Lighten = LIGHTEN_MAX;

    switch(TowerType)
    {
    case TOWER_BEAT_RIFLE:
      Wrld->AddToWorld(new Bolt(this, arg.Object, 0.1f));
      break;
    case TOWER_CHIPSTEP_COIL:
      Wrld->AddToWorld(new Bolt(this, arg.Object, 0.2f));
      break;
    case TOWER_SNARE_GUN:
      Wrld->AddToWorld(new Spark(this, arg.Object));
      break;
    case TOWER_BASE_CANNON:
      Wrld->AddToWorld(new Spark(this, arg.Object, true));
      break;
    }
  }
}